/*
 * @(#)ProviderRegistry.java 1.0 2000/12/14
 * 
 * Copyright 2000 Thomas Barnekow. All Rights Reserved.
 * 
 * This software is the proprietary information of Thomas Barnekow. Use is
 * subject to license terms.
 */

package tb.provider;

import java.util.*;
import tb.util.*;

/**
 * A {@link Provider}registry that is used to find registered service
 * providers.
 * 
 * @author Thomas Barnekow
 * @version 1.0
 */
public class ProviderRegistry {

	/**
	 * Service providers by name.
	 */
	private static Map m_providersByName = new HashMap();

	/**
	 * Provider {@link java.util.List}s by service
	 */
	private static Map m_providersByService = new HashMap();

	/**
	 * Services
	 */
	private static List m_services = new ArrayList();

	/**
	 * Modification flag: TRUE => can modify, FALSE => can't modify anything.
	 */
	private static volatile int m_count = 0;

	/**
	 * Modification semaphore.
	 */
	private static Object m_semaphore = new Object();

	/**
	 * Don't let them create instances.
	 */
	private ProviderRegistry() {
	}

	/*
	 * Synchronization section
	 */

	private static void incrementCount() {
		synchronized (m_semaphore) {
			m_count++;
		}
	}

	private static void decrementCount() {
		synchronized (m_semaphore) {
			if (--m_count == 0)
				m_semaphore.notifyAll();
		}
	}

	/*
	 * Services section
	 */

	private static Set addService(QualifiedName service) {
		Set providers = null;
		if (Collections
			.binarySearch(m_services, service, Collections.reverseOrder())
			< 0) {
			// Add service to the sorted list of services.
			m_services.add(service);
			Collections.sort(m_services, Collections.reverseOrder());

			// Map service to empty set of providers
			providers = new HashSet();
			m_providersByService.put(service, providers);
		} else {
			providers = (Set) m_providersByService.get(service);
		}
		return providers;
	}

	private static void removeService(QualifiedName service) {
		m_services.remove(service);
		m_providersByService.remove(service);
	}

	/**
	 * Register a new service provider. Happily overwrite an existing entry.
	 */
	public static void registerProvider(
		QualifiedName service,
		Provider provider) {
		try {
			synchronized (m_semaphore) {
				while (m_count > 0)
					m_semaphore.wait();

				// Add to map: name -> provider
				m_providersByName.put(provider.getQualifiedName(), provider);

				// Add to map: service -> providers
				addService(service).add(provider);
			}
		} catch (InterruptedException ex) {
		}
	}

	/**
	 * Deregister a single service provider.
	 */
	public static void deregisterProvider(Provider provider) {
		try {
			synchronized (m_semaphore) {
				while (m_count > 0)
					m_semaphore.wait();

				// Remove from map: name -> provider
				m_providersByName.remove(provider.getQualifiedName());

				// Remove from map: service -> providers
				Iterator iterator = m_services.iterator();
				while (iterator.hasNext()) {
					QualifiedName service = (QualifiedName) iterator.next();
					Set providers = (Set) m_providersByService.get(service);
					providers.remove(provider);
					if (providers.size() == 0)
						removeService(service);
				}
			}
		} catch (InterruptedException ex) {
		}
	}

	/**
	 * Deregister all service providers.
	 */
	public static void deregisterProviders() {
		try {
			synchronized (m_semaphore) {
				while (m_count > 0)
					m_semaphore.wait();
				m_providersByName.clear();
				m_providersByService.clear();
				m_services.clear();
			}
		} catch (InterruptedException ex) {
		}
	}

	/**
	 * Find a registered service provider by name.
	 */
	public static Provider findProvider(QualifiedName name)
		throws NoSuchElementException {
		if (name == null) {
			throw new IllegalArgumentException("Name must not be null");
		}

		incrementCount();
		Provider provider = (Provider) m_providersByName.get(name);
		decrementCount();

		if (provider != null) {
			return provider;
		} else {
			throw new NoSuchElementException("No such provider: " + name);
		}
	}

	/**
	 * Find a registered service provider by name.
	 */
	public static Provider findProvider(String name)
		throws NoSuchElementException {
		return findProvider(new QualifiedName(name));
	}

	/**
	 * Find a registered service provider by class, service, and entity.
	 * 
	 * @param clazz
	 *            service provider's class or interface
	 * @param service
	 *            service element or compound service
	 * @param entity
	 *            element or container
	 */
	public static Provider findProvider(
		Class clazz,
		QualifiedName service,
		QualifiedName entity)
		throws NoSuchElementException {
		if (service == null
			|| service.nameComponents() == 0
			|| entity == null
			|| entity.nameComponents() == 0) {
			throw new IllegalArgumentException();
		}

		incrementCount();
		try {
			QualifiedName rs = service;
			while (rs.nameComponents() > 0) {
				Set providers = null;

				// Find a list of registered tentative service providers
				while (rs.nameComponents() > 0
					&& (providers = (Set) m_providersByService.get(rs)) == null) {
					rs = rs.getNamespaceName();
				}
				if (providers == null) {
					throw new NoSuchElementException(
						"No \"" + service + "\" provider for " + entity);
				}

				// OK, we have found us some possible service providers.
				// Probe each one in turn.
				Iterator iterator = providers.iterator();
				while (iterator.hasNext()) {
					Provider provider = (Provider) iterator.next();
					if (clazz.isInstance(provider)
						&& provider.isProvider(rs, entity))
						return provider;
				}

				// No provider found yet. Try next-level compound service.
				rs = rs.getNamespaceName();
			}
		} finally {
			decrementCount();
		}

		// We have not found a service provider.
		throw new NoSuchElementException(
			"No \"" + service + "\" provider for " + entity);
	}
}
